lg = love.graphics
lg.color = lg.setColor

local Snek = require("snek")
local Object = require("block")
local Button = require("button")

function lerp(a, b, t)
	return a * (1 - t) + b * t
end

function dist(x1, y1, x2, y2)
	return ((x2 - x1) ^ 2 + (y2 - y1) ^ 2) ^ 0.5
end

function angle(x1, y1, x2, y2)
	return math.atan2(y2 - y1, x2 - x1)
end

blocks = {}
function blocks.shift()
	local bf = blocks[1]
	local bl = blocks[#blocks]
	bf:shift(bl)
	table.remove(blocks, 1)
	table.insert(blocks, bf)
end
function blocks.clear()
	for i = #blocks, 1, -1 do
		table.remove(blocks, i)
	end
end

local isAiming = false
local aim = { x = 0, y = 0, startx = 0, starty = 0 }

local bkgSprite = lg.newImage("sprites/bkg.png")
scaleMin = 1
scaleMax = 1

local leavesSprite = lg.newImage("sprites/leaves.png")
local woodSprite = lg.newImage("sprites/wood.png")
local woodQuad = lg.newQuad(0, 0, 800 * 2, 600 * 2, woodSprite)
local leavesQuad = lg.newQuad(0, 0, 800 * 2, 600, leavesSprite)
woodSprite:setWrap("repeat")
leavesSprite:setWrap("repeat", "clampzero")
local woodOffset = { x = 0, y = 0 }

overlayShader = love.graphics.newShader([[
	uniform vec4 overlay_color;
	vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){
		vec4 pixel = Texel(texture,texture_coords);
		pixel.r = overlay_color.r;
		pixel.g = overlay_color.g;
		pixel.b = overlay_color.b;
		return pixel;
}
]])

local font = lg.setNewFont("lampfont.ttf", 22)
font:setFilter("linear", "nearest")

local defaultCursors = love.mouse.getSystemCursor("arrow")

STATES = { MENU = 1, GAME = 2 }
state = STATES.MENU

score = 0
highScore = 0
milestoneStep = 100
nextMilestone = milestoneStep

music = {
	menu = love.audio.newSource("audio/musicMenu.ogg", "static"),
	game = love.audio.newSource("audio/musicGame.ogg", "static"),
}

sfx = {
	stretch = love.audio.newSource("audio/stretchSfx.ogg", "stream"),
	jump = love.audio.newSource("audio/jumpSfx.ogg", "stream"),
	land = love.audio.newSource("audio/landSfx.ogg", "stream"),
	perfect = love.audio.newSource("audio/perfectSfx.ogg", "stream"),
	fruit = love.audio.newSource("audio/fruitSfx.ogg", "stream"),
	fall = love.audio.newSource("audio/fallSfx.ogg", "stream"),
	death = love.audio.newSource("audio/deathSfx.ogg", "stream"),
}

local icons = {
	icon = lg.newImage("sprites/icon.png"),
	score = lg.newImage("sprites/iconScore.png"),
	high = lg.newImage("sprites/iconHigh.png"),
	mute = lg.newImage("sprites/iconMute.png"),
	unmute = lg.newImage("sprites/iconUnmute.png"),
	uldo = lg.newImage("sprites/iconUldo.png"),
}
local muteButton = Button:new(lg:getWidth() - 32 * 2 - 10, lg:getHeight() - 32, icons.unmute, function(s)
	s.icon = s.icon == icons.unmute and icons.mute or icons.unmute
	if s.icon == icons.unmute then
		for k, sf in pairs(sfx) do
			sf:setVolume(1)
		end
		for k, m in pairs(music) do
			m:setVolume(0.5)
		end
	else
		for k, sf in pairs(sfx) do
			sf:setVolume(0)
		end
		for k, m in pairs(music) do
			m:setVolume(0)
		end
	end
end)
local uldoButton = Button:new(lg:getWidth() - 32, lg:getHeight() - 32, icons.uldo, function()
	love.system.openURL("https://uldo.su/")
end)
local logo = lg.newImage("sprites/logo.png")

function startGame()
	blocks.clear()
	for i = 0, 30 do
		local b = Object:new(i * 200 + i * 300, 400 - i * 50, 64, 100, "block")
		table.insert(blocks, b)
		if i > 0 then
			b:shift(blocks[i])
		end
	end
	fruit = Object:new(0, 0, 64 * 1.5, 64 * 1.5, "fruit")
	fruit:shift()
	snek = Snek:new()
	music.game:seek(music.menu:tell())
	music.menu:stop()
	music.game:play()
end

function love.load()
	lg.setLineWidth(7)
	overlayShader:send("overlay_color", { 1.0, 1.0, 1.0, 1.0 })
	--love.resize(lg:getWidth(), lg:getHeight())
	if love.system.getOS() == "Android" then
		love.window.setMode(lg.getWidth(), lg.getHeight(), { resizable = false, fullscreen = true })
	end
	if love.filesystem.getInfo("save.txt") then
		file = love.filesystem.read("save.txt")
		highScore = tonumber(file)
	end
	music.menu:setLooping(true)
	music.game:setLooping(true)
	music.game:setVolume(0.5)
	music.menu:setVolume(0.5)
	music.menu:play()
end

function love.resize(w, h)
	woodQuad:setViewport(0, 0, math.max(w * 2, 800 * 2), math.max(h * 2, 600 * 2), 800, 600)
	scaleMax = math.max(w / 800, h / 600)
	scaleMin = math.min(w / 800, h / 600)
	leavesQuad:setViewport(0, 0, math.max(800 * 2, w * 2), 600, 800, 600)
	uldoButton.x = w - 32 * scaleMin - 30 * scaleMin
	uldoButton.y = h - 32 * scaleMin - 15 * scaleMin
	muteButton.x = w - 32 * scaleMin * 2 - 40 * scaleMin
	muteButton.y = h - 32 * scaleMin - 15 * scaleMin
end

function love.update(dt)
	if state == STATES.GAME then
		local svy = snek.vy + dt * 9.5
		for i, b in ipairs(blocks) do
			b:update(dt, snek.vx, svy)
		end
		fruit:update(dt, snek.vx, svy)
		snek:update(dt)

		woodOffset.x = woodOffset.x - snek.vx * 0.1
		woodOffset.y = woodOffset.y - snek.vy * 0.5
		if woodOffset.x < -800 then
			woodOffset.x = 0
		elseif woodOffset.x > 0 then
			woodOffset.x = -800
		end
		if woodOffset.y < -600 then
			woodOffset.y = 0
		elseif woodOffset.y > 0 then
			woodOffset.y = -600
		end
	elseif state == STATES.MENU then
		local mx, my = love.mouse.getPosition()
		if not uldoButton:update(dt, mx, my) and not muteButton:update(dt, mx, my) then
			love.mouse.setCursor(defaultCursors)
		end
	end
end

local bkgColor = { 45 / 255, 45 / 255, 56 / 255 }
function love.draw()
	local iconScale = 0.3 * scaleMin
	local iconSize = 128 * iconScale
	local iconPadding = 5 * scaleMin
	local iconBkgPadding = 8 * scaleMin
	lg.draw(bkgSprite, 0, 0, 0, scaleMax)
	local offset = 8
	local w, h = lg:getWidth(), lg:getHeight()

	if state == STATES.GAME then
		lg.push()
		lg.translate(-snek.x + w / 3, -snek.y + h / 1.5)
		for i = #blocks, 1, -1 do
			local b = blocks[i]
			b:drawBehind()
		end
		lg.pop()
	end

	lg.color(0, 0, 0)
	lg.rectangle("fill", w / 8 - offset, 0, w / 4 * 3 + offset * 2, h)
	lg.color(1, 1, 1)
	lg.setScissor(w / 8, 0, w / 4 * 3, h)
	lg.draw(woodSprite, woodQuad, woodOffset.x + w / 8, woodOffset.y)
	lg.color(0, 0, 0.3, 0.3)
	lg.draw(leavesSprite, leavesQuad, woodOffset.x + 15, 15)
	if state == STATES.GAME then
		lg.push()
		lg.translate(-snek.x + w / 3, -snek.y + h / 1.5)
		for i = #blocks, 1, -1 do
			local b = blocks[i]
			b:drawShade()
		end
		lg.pop()
	end
	lg.color(1, 1, 1)
	lg.setScissor()

	if state == STATES.GAME then
		lg.push()
		lg.translate(-snek.x + w / 3, -snek.y + h / 1.5)
		for i = #blocks, 1, -1 do
			local b = blocks[i]
			b:draw()
		end
		fruit:draw()
		local mx, my = love.mouse.getPosition()
		local p = math.min(dist(aim.startx, aim.starty, mx, my) / 2, 300)
		lg.color(p / 300, (400 - p) / 300, 0.3)
		lg.line(snek.x, snek.y, snek.x + aim.x * p, snek.y + aim.y * p)
		snek:draw()
		lg.pop()

		if aim.startx > 0 or aim.starty > 0 then
			lg.color(1, 1, 1, 0.5)
			lg.circle("line", aim.startx, aim.starty, 15)
			lg.circle("line", mx, my, 15)
		end
		lg.color(1, 1, 1)

		lg.draw(leavesSprite, leavesQuad, woodOffset.x, 0)
		local cursor = { x = iconBkgPadding * 2, y = 70 - iconBkgPadding }
		lg.color(0, 0, 0, 0.75)
		local maxTextWidth = math.max(
			font:getWidth("x" .. snek.doubleJumps),
			font:getWidth("x" .. snek.precisionJumps),
			font:getWidth("x" .. snek.stoneJumps),
			font:getWidth("x" .. snek.lives),
			font:getWidth(" " .. score)
		)
		lg.rectangle(
			"fill",
			cursor.x - iconBkgPadding,
			cursor.y - iconBkgPadding,
			iconSize + maxTextWidth * scaleMin + iconBkgPadding * 2,
			iconSize * 5 + iconPadding * 5 + iconBkgPadding * 2,
			8 * scaleMin
		)
		lg.color(1, 1, 1)
		local function drawIcon(sprite, x, y)
			local iconOffset = 2 * scaleMin
			lg.setShader(overlayShader)
			for x = -1, 1 do
				for y = -1, 1 do
					lg.draw(sprite, cursor.x + x * iconOffset, cursor.y + y * iconOffset, 0, iconScale)
				end
			end
			lg.setShader()
			lg.draw(sprite, cursor.x, cursor.y, 0, iconScale)
			cursor.x = cursor.x + iconSize
		end
		local function printValue(value, x, y)
			lg.print(value, x, y + font:getHeight() * scaleMin / 4, 0, scaleMin)
			cursor.y = cursor.y + iconSize + iconPadding
			cursor.x = iconBkgPadding + iconPadding
		end
		drawIcon(fruitSprites["double"], cursor.x, cursor.y)
		printValue("x" .. snek.doubleJumps, cursor.x, cursor.y)
		drawIcon(fruitSprites["precision"], cursor.x, cursor.y)
		printValue("x" .. snek.precisionJumps, cursor.x, cursor.y)
		drawIcon(fruitSprites["stone"], cursor.x, cursor.y)
		printValue("x" .. snek.stoneJumps, cursor.x, cursor.y)
		drawIcon(icons.icon, cursor.x, cursor.y)
		printValue("x" .. snek.lives, cursor.x, cursor.y)
		drawIcon(icons.score, cursor.x, cursor.y)
		printValue(" " .. score, cursor.x, cursor.y)
	elseif state == STATES.MENU then
		lg.draw(leavesSprite, leavesQuad, woodOffset.x, 0)
		lg.draw(logo, w / 2, h / 2 - 20 * scaleMin, 0, scaleMin, scaleMin, 400, 300)
		lg.printf("PRESS ANYWHERE TO START", 0, h / 2 + 200 * scaleMin, w / scaleMin, "center", 0, scaleMin)
		local cursor = { x = iconBkgPadding * 2, y = h - iconSize - iconPadding - iconBkgPadding }
		lg.color(0, 0, 0, 0.75)
		lg.rectangle(
			"fill",
			cursor.x - iconBkgPadding,
			cursor.y - iconBkgPadding,
			iconSize * 2 + iconPadding * 2 + iconBkgPadding * 2 + font:getWidth(highScore) * scaleMin,
			iconSize + iconBkgPadding * 2,
			8 * scaleMin
		)
		lg.color(1, 1, 1)
		local function drawIcon(sprite, x, y)
			local iconOffset = 2 * scaleMin
			lg.setShader(overlayShader)
			for x = -1, 1 do
				for y = -1, 1 do
					lg.draw(sprite, cursor.x + x * iconOffset, cursor.y + y * iconOffset, 0, iconScale)
				end
			end
			lg.setShader()
			lg.draw(sprite, cursor.x, cursor.y, 0, iconScale)
			lg.draw(sprite, cursor.x, cursor.y, 0, iconScale)
			cursor.x = cursor.x + iconSize + iconPadding
		end
		drawIcon(icons.high)
		drawIcon(icons.score)
		lg.setBlendMode("alpha")
		lg.print(highScore, cursor.x, cursor.y + iconSize / 4, 0, scaleMin)
		lg.color(0, 0, 0, 0.75)
		cursor.x = muteButton.x
		cursor.y = muteButton.y
		lg.rectangle(
			"fill",
			cursor.x - iconBkgPadding,
			cursor.y - iconBkgPadding,
			iconSize * 2 + iconPadding * 2 + iconBkgPadding * 2,
			iconSize + iconBkgPadding * 2,
			8 * scaleMin
		)
		lg.color(1, 1, 1)
		uldoButton:draw()
		muteButton:draw()
	end

	lg.color(1, 1, 1)
end

function love.mousepressed(x, y, b)
	if state == STATES.GAME then
		if b == 1 and snek.onGround then
			isAiming = true
			aim.startx, aim.starty = love.mouse.getPosition()
			aim.x, aim.y = aim.startx, aim.starty
		end
	end
end

function love.mousereleased(x, y, b, istouch, presses)
	if b == 1 and #love.touch.getTouches() <= 1 then
		if state == STATES.GAME then
			if isAiming then
				isAiming = false
				local p = math.min(dist(aim.startx, aim.starty, x, y) / 50, 20)
				snek:jump(aim.x, aim.y, p)
				aim = { x = 0, startx = 0, y = 0, starty = 0, lastX = aim.x, lastY = aim.y, lastP = p }
			elseif not snek.onGround then
				if snek.doubleJumps > 0 then
					snek:jump(aim.lastX, aim.lastY, aim.lastP)
				end
			end
		elseif state == STATES.MENU then
			if muteButton:checkClick(x, y) then
				muteButton:fn()
				return
			elseif uldoButton:checkClick(x, y) then
				uldoButton.fn()
				return
			end
			startGame()
			state = STATES.GAME
		end
	elseif b == 2 and state == STATES.GAME then
		if not snek.onGround and snek.stoneJumps > 0 then
			snek:stop()
		end
	end
end

function love.touchpressed()
	if #love.touch.getTouches() > 1 then
		if not snek.onGround and snek.stoneJumps > 0 then
			snek:stop()
		end
	end
end

function love.mousemoved(x, y, dx, dy)
	if state == STATES.GAME then
		if isAiming then
			local d = angle(x, y, aim.startx, aim.starty)
			local p = math.min(dist(aim.startx, aim.starty, x, y) / 50, 20)
			aim.x = math.max(0, math.cos(d))
			aim.y = math.min(0, math.sin(d))
			sfx.stretch:setPitch(p / 20 * 2 + 1)
			sfx.stretch:play()
		end
	end
end
